﻿using System;
using Glue.Converters;

namespace Glue.Internals.Verification
{
    internal class RandomValueGenerator
    {
        private readonly object oldValue;
        private readonly Type generateObjectOfThisType;
        private readonly Type originalObjectIsOfThisType;
        private readonly IConverter converterBetweenTypes;
        private readonly ConvertibleRandomValues randomizers;

        internal RandomValueGenerator(object originalValue, Type generateObjectOfThisType, Type originalObjectIsOfThisType,
            IConverter converterBetweenTypes, ConvertibleRandomValues randomizers)
        {
            oldValue = originalValue;
            this.generateObjectOfThisType = generateObjectOfThisType;
            this.originalObjectIsOfThisType = originalObjectIsOfThisType;
            this.converterBetweenTypes = converterBetweenTypes;
            this.randomizers = randomizers;
        }

        public object GenerateValue()
        {
            var newValue = TryGenerateValue() ?? TryGenerateValueThroughConverter();

            return newValue;
        }

        private object TryGenerateValue()
        {
            for (int i = 0; i < 50; i++)
            {
                var randomValue = GetRandomValue(originalObjectIsOfThisType, generateObjectOfThisType);
                var newValue = converterBetweenTypes.Map(randomValue, oldValue);
                if (!newValue.Equals(oldValue))
                    return newValue;
            }

            return null;
        }

        private object TryGenerateValueThroughConverter()
        {
            for (int i = 0; i < 50; i++)
            {
                var randomValueReverse = GetRandomValue(generateObjectOfThisType, originalObjectIsOfThisType);
                var newValueReverse = converterBetweenTypes.Map(randomValueReverse, GetRandomValue(originalObjectIsOfThisType, generateObjectOfThisType));
                var newValue = converterBetweenTypes.Map(newValueReverse, oldValue);
                if (!newValue.Equals(oldValue))
                    return newValue;
            }
            return null;
        }

        private object GetRandomValue(Type ofThisType, Type convertibleToThisType)
        {
            try
            {
                return randomizers.GetRandomValue(ofThisType, convertibleToThisType);
            }
            catch (GlueException ex)
            {
                if (ofThisType.IsPrimitive())
                {
                    throw new GlueException(ex.Message + " ('" + ofThisType.Name + "' is not a valuetype. Have you forgotten to handle a nested relation?)");
                }
                throw;
            }
        }
    }
}
